#include <OgreManualObject.h>
#include <OgreRenderOperation.h>
#include <ctype.h>
#include "HeightMap.h"
#include "application.h"
//#include <windows.h>

#include "NxPhysics.h"
//#include "NxCooking.h"

#include "NxHeightField.h"
#include "NxHeightFieldDesc.h"
#include "NxHeightFieldShapeDesc.h"
#include "NxHeightFieldSample.h"

#define HEIGHTMAP_SCALE (1./75.)

static NxActor*			gHeightField = NULL;

#define hmHeight(img,x,y) (img.getColourAt(x,y,0).getAsARGB() & 0x0000FF)
#define hmTile1(img,x,y) (((img.getColourAt(x,y,0).getAsARGB() & 0x00FF00)>>8)/8)
#define hmTile2(img,x,y) (((img.getColourAt(x,y,0).getAsARGB() & 0xFF0000)>>16)/8)

HeightMap::HeightMap()
{
    mGeometry = NULL;
	
}

HeightMap::~HeightMap()
{
    if(mGeometry)
	{
		gSceneMgr->destroyManualObject(mGeometry);
	}
}

Ogre::ManualObject* HeightMap::getGeometry()
{
    if(!mGeometry)
    {
        createGeometry();
    }
    return mGeometry;
}

Ogre::Vector3 HeightMap::getVertexNormal(Ogre::Image& img, int x, int y)
{
	Ogre::Vector3 topleft(getFaceNormal(img, x-1, y-1));
	Ogre::Vector3 topright(getFaceNormal(img,x, y-1));
	Ogre::Vector3 bottomleft(getFaceNormal(img,x-1, y));
	Ogre::Vector3 bottomright(getFaceNormal(img,x, y));

	return (topleft + topright + bottomleft + bottomright)/4;
}
Ogre::Vector3 HeightMap::getFaceNormal(Ogre::Image& img, int x, int y)
{
	if (x < 0) x = 0;
	if (y < 0) y = 0;
	Ogre::Vector3 bottomleft	(x,		hmHeight(img,x,y)*HEIGHTMAP_SCALE,		y);
	Ogre::Vector3 bottomright	(x+1,	hmHeight(img,x+1, y)*HEIGHTMAP_SCALE,	y);
	Ogre::Vector3 topleft		(x,		hmHeight(img,x,y+1)*HEIGHTMAP_SCALE,	y+1);
	Ogre::Vector3 topright		(x+1,	hmHeight(img,x+1,y+1)*HEIGHTMAP_SCALE,	y+1);
	
	Ogre::Vector3 diag1 = bottomright - topleft;
	Ogre::Vector3 diag2 = bottomleft - topright;

	Ogre::Vector3 normal = diag1.crossProduct(diag2).normalisedCopy();

	return normal;
}
void HeightMap::putElement(Ogre::Image& img, int tile1, int tile2, int x, int y, Ogre::ManualObject* geometry, int ox, int oy)
{
	if (x+ox == img.getWidth()) x--;
	if (y+oy == img.getHeight()) y--;
    geometry->position(
        (y+oy),
        (hmHeight(img,x+ox,y+oy)*HEIGHTMAP_SCALE),
        (x+ox)
    );
    geometry->colour(1,1,1/*img.getColourAt(x,y,0)*/);
	geometry->normal(getVertexNormal(img,x+ox,y+oy));
	geometry->textureCoord(
        ((tile1%8)*128. +1. +ox*126.)/1024.,
        ((tile1/8)*128. +1. +oy*126.)/512.
    );
	double ox_d = ox / 2.;
	double oy_d = oy / 2.;
	ox_d += (x % 2) / 2.;
	oy_d += (y % 2) / 2.;
	geometry->textureCoord(
        ((tile2%8)*128. +1. +ox_d*126.)/1024.,
        ((tile2/8)*128. +1. +oy_d*126.)/512.
    );
    
}

void HeightMap::createGeometry()
{
    // load an image
    Ogre::Image img;
    img.load("001_h.png","General");



    // get its width and height
    int w=img.getWidth(), h=img.getHeight();

    // create an ogre "manual object" for storing heightmap
    // TODO
    mGeometry = gSceneMgr->createManualObject("heightmap");
	//gSceneMgr->getRootSceneNode()->attachObject(mGeometry);
    mGeometry->estimateVertexCount(w*h*4); // these 2 lines help ogre by preallocating required memory
    mGeometry->estimateIndexCount(w*h*6);

    // begin manual object section using material grass_asphalt; we'll list triangles
	mGeometry->begin("grass_asphalt", Ogre::RenderOperation::OT_TRIANGLE_LIST);


	Ogre::ManualObject* tmp_wireframe=gSceneMgr->createManualObject("heightmap_wireframe_debug"); // remove this when wireframe aint needed
	tmp_wireframe->begin("wireframe_material", Ogre::RenderOperation::OT_LINE_LIST);
	
	// NxVec3* nx_verts = new NxVec3[h*w];

    NxVec3 pos = NxVec3 (0,-1,0);
	NxVec3 size = NxVec3(10,1,10);

	NxHeightFieldDesc heightFieldDesc;
	heightFieldDesc.nbColumns		= w;
	heightFieldDesc.nbRows			= h-1;
	heightFieldDesc.convexEdgeThreshold = 0;

	heightFieldDesc.samples			= new NxU32[w*(h-1)];
	char* currentByte = (char*)heightFieldDesc.samples;


	
	heightFieldDesc.sampleStride	= sizeof(NxU32);  

    // fill the manual object with 3d data
    for(int y=0;y<h-1;y++)
    {
        for (int x=0;x<w;x++)
        {
			int tile1 = hmTile1(img,x,y);
			int tile2 = hmTile2(img,x,y);			 
				
			if (tile1 || tile2) {  // 0 marks "tile doesnt exist" 
				if (!tile1) tile1 = tile2; else
				if (!tile2) tile2 = tile1;

				tile1--;  // 1 marks "0th tile on image", 2 marks "1th tile on image" etc
				tile2--;

				
				putElement(img,tile1,tile2,x,y,mGeometry,1,1);
				putElement(img,tile1,tile2,x,y,mGeometry,0,1);
				putElement(img,tile1,tile2,x,y,mGeometry,0,0);

				putElement(img,tile1,tile2,x,y,mGeometry,0,0);
				putElement(img,tile1,tile2,x,y,mGeometry,1,0);
				putElement(img,tile1,tile2,x,y,mGeometry,1,1);
			}

			putElement(img,tile1,tile2,x,y,tmp_wireframe,1,1);
			putElement(img,tile1,tile2,x,y,tmp_wireframe,0,1); putElement(img,tile1,tile2,x,y,tmp_wireframe,0,1);
			putElement(img,tile1,tile2,x,y,tmp_wireframe,0,0); putElement(img,tile1,tile2,x,y,tmp_wireframe,0,0);
			putElement(img,tile1,tile2,x,y,tmp_wireframe,1,0); putElement(img,tile1,tile2,x,y,tmp_wireframe,1,0);
			putElement(img,tile1,tile2,x,y,tmp_wireframe,1,1); putElement(img,tile1,tile2,x,y,tmp_wireframe,1,1);
			putElement(img,tile1,tile2,x,y,tmp_wireframe,0,0); 

			
			////////////////////////// END OGRE HM CODE ///////////////





			///// PHYSICS HM CODE /////////


			NxHeightFieldSample* currentSample = (NxHeightFieldSample*)currentByte;
			currentSample->height = hmHeight(img,x,y);
			currentSample->tessFlag = 0;
			currentSample->materialIndex0 = 0;
			currentSample->materialIndex1 = 0;

			
			currentByte += heightFieldDesc.sampleStride;

			///// PHYSICS HM CODE /////////

        }
    }
	tmp_wireframe->end();
	tmp_wireframe->setRenderQueueGroup(70);
	//gSceneMgr->getRootSceneNode()->attachObject (tmp_wireframe);
	

	NxHeightField* heightField = gPhysicsSDK->createHeightField(heightFieldDesc);
	

	// data has been copied, we can free our buffer
	delete [] heightFieldDesc.samples;

	NxHeightFieldShapeDesc heightFieldShapeDesc;
	heightFieldShapeDesc.heightField	= heightField;
	heightFieldShapeDesc.shapeFlags		= NX_SF_FEATURE_INDICES | NX_SF_VISUALIZATION;
	heightFieldShapeDesc.heightScale	= HEIGHTMAP_SCALE;
	heightFieldShapeDesc.rowScale		= 1;//w;
	heightFieldShapeDesc.columnScale	= 1;//h;
	//heightFieldShapeDesc.meshFlags	= NX_MESH_SMOOTH_SPHERE_COLLISIONS;
	heightFieldShapeDesc.materialIndexHighBits = 0;
	heightFieldShapeDesc.holeMaterial = 2;

	
	//// physics margin planes ////
	
	// #1
	NxPlaneShapeDesc planeDesc1;
	NxActorDesc actorDesc1;
			
	planeDesc1.normal = NxVec3(1.0f,0.0f,0.0f);
	planeDesc1.d = 0;

	actorDesc1.shapes.pushBack(&planeDesc1);

	NxActor *planeActor1 = gScene->createActor(actorDesc1);

	// #2
	NxPlaneShapeDesc planeDesc2;
	NxActorDesc actorDesc2;
			 
	planeDesc2.normal = NxVec3(0.0f,0.0f,1.0f);
	planeDesc2.d = 0;

	actorDesc2.shapes.pushBack(&planeDesc2);

	NxActor *planeActor2 = gScene->createActor(actorDesc2);

	// #3
	NxPlaneShapeDesc planeDesc3;
	NxActorDesc actorDesc3;
			
	planeDesc3.normal = NxVec3(-1.0f,0.0f,0.0f);
	planeDesc3.d = -w;

	actorDesc3.shapes.pushBack(&planeDesc3);

	NxActor *planeActor3 = gScene->createActor(actorDesc3);

	// #4
	NxPlaneShapeDesc planeDesc4;
	NxActorDesc actorDesc4;
			
	planeDesc4.normal = NxVec3(0.0f,0.0f,-1.0f);
	planeDesc4.d = -h;
	
	actorDesc4.shapes.pushBack(&planeDesc4);

	NxActor *planeActor4 = gScene->createActor(actorDesc4);

	//// physics margin planes ////


	NxActorDesc actorDesc;
	actorDesc.shapes.pushBack(&heightFieldShapeDesc);
	actorDesc.body		   = NULL;
	actorDesc.globalPose.t = NxVec3(0,0,0);
	NxActor* newActor = gScene->createActor(actorDesc);

	gHeightField = newActor;
/*
	const NxHeightFieldShape * hfs = (const NxHeightFieldShape*)gHeightField->getShapes()[0];

	NxU32 flags;
	NxTriangle tri;
	NxTriangle edgeTri;

	for (int i=0;i<w*h*2;i++)
	{
		hfs->getTriangle(tri, &edgeTri, &flags, i, true);
		for (int j=0;j<3;j++)
		{
			
			mGeometry->position(tri.verts[j].x,tri.verts[j].y,tri.verts[j].z);
			mGeometry->textureCoord(tri.verts[j].x/100.0f,tri.verts[j].z/100);
		}
	}
*/
	mGeometry->end();
}
